﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using ScrewTurn.Wiki.PluginFramework;

namespace BoxPlugin
{
    public class Box : IFormatterProviderV30
    {
        private IHostV30 _host;
        private string _config;
        private bool _enableLogging = true;
        private static readonly ComponentInformation Info = new ComponentInformation("Box Plugin", "SysTell s.c.", "1.0.0.1", "http://www.systell.pl", "");

        private static readonly Regex XmlRegex = new Regex(@"\<box(.*?)\>(.*?)\<\/box\>",
            RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

        /// <summary>
        /// Specifies whether or not to execute Phase 1.
        /// </summary>
        public bool PerformPhase1
        {
            get { return false; }
        }

        /// <summary>
        /// Specifies whether or not to execute Phase 2.
        /// </summary>
        public bool PerformPhase2
        {
            get { return false; }
        }

        /// <summary>
        /// Specifies whether or not to execute Phase 3.
        /// </summary>
        public bool PerformPhase3
        {
            get { return true; }
        }

        /// <summary>
        /// Gets the execution priority of the provider (0 lowest, 100 highest).
        /// </summary>
        public int ExecutionPriority
        {
            get { return 50; }
        }

        /// <summary>
        /// Performs a Formatting phase.
        /// </summary>
        /// <param name="raw">The raw content to Format.</param>
        /// <param name="context">The Context information.</param>
        /// <param name="phase">The Phase.</param>
        /// <returns>The Formatted content.</returns>
        public string Format(string raw, ContextInformation context, FormattingPhase phase)
        {
            // <countDownloads pattern="..."[ startDate="yyyy/mm/dd"]>
            //   <file name="..."[ provider="..."] />
            //   <attachment name="..." page="..."[ provider="..."] />
            // </countDownloads>
            // All downloads are grouped together
            // Pattern placeholders: #COUNT#, #DAILY#, #WEEKLY#, #MONTHLY# (case insensitive)
            // Pattern example: "Downloaded #COUNT# times (#MONTHLY#/month)!"
            // StartDate omitted -> 2009/01/01
            // Provider omitted -> default
            // File/attachment/page not found -> ignored

            StringBuilder buffer = new StringBuilder(raw);

            KeyValuePair<int, string> block = FindAndRemoveFirstOccurrence(buffer);

            while (block.Key != -1)
            {
                string blockHash = "Box-" + block.Value.ToString();

                string result = null;

                if (System.Web.HttpContext.Current != null)
                {
                    result = System.Web.HttpContext.Current.Cache[blockHash] as string;
                }
                
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(block.Value);

                string color;
                string title;
                string icon;
                string height;
                GetRootAttributes(doc, out color, out title, out icon, out height);

                string boxInnerXml;
                GetInnerXml(doc, out boxInnerXml);

                result = BuildResult(color, icon, title, boxInnerXml, height);
                
                if (System.Web.HttpContext.Current != null)
                {
                    System.Web.HttpContext.Current.Cache.Add(blockHash, result, null, DateTime.Now.AddMinutes(10),
                        System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
                }            

                buffer.Insert(block.Key, result);

                block = FindAndRemoveFirstOccurrence(buffer);
            }

            return buffer.ToString();
        }

        /// <summary>
        /// Builds the result.
        /// </summary>
        /// <param name="pattern">The result pattern.</param>
        /// <param name="downloads">The downloads.</param>
        /// <param name="daily">The daily downloads.</param>
        /// <param name="weekly">The weekly downloads.</param>
        /// <param name="monthly">The monthly downloads.</param>
        /// <returns>The result.</returns>
        private static string BuildResult(string color, string icon, string title, string innerXml, string height)
        {
            StringBuilder result = new StringBuilder();

            result.Append(string.Format("<div class=\"box_{0}\">\n", color));
            result.Append("\t<div class=\"top\">\n");
            result.Append("\t\t<div class=\"left\">\n");
            result.Append("\t\t\t<div class=\"bottom\">\n");
            result.Append("\t\t\t\t<div class=\"right\">\n");
            result.Append("\t\t\t\t\t<div class=\"left_top\">\n");
            result.Append("\t\t\t\t\t\t<div class=\"right_top\">\n");
            result.Append("\t\t\t\t\t\t\t<div class=\"left_bottom\">\n");
            result.Append("\t\t\t\t\t\t\t\t<div class=\"right_bottom\">\n");
            result.Append("\t\t\t\t\t\t\t\t\t<div class=\"box_header\">\n");
            result.Append("\t\t\t\t\t\t\t\t\t\t<div class=\"icon\">\n");
            result.Append(string.Format("\t\t\t\t\t\t\t\t\t\t\t<img src=\"Images/box/{0}.png\" />\n", icon));
            result.Append("\t\t\t\t\t\t\t\t\t\t</div>\n");
            result.Append(string.Format("\t\t\t\t\t\t\t\t\t\t<h1>{0}</h1>\n", title));
            result.Append("\t\t\t\t\t\t\t\t\t</div>\n");
            if (String.IsNullOrEmpty(height))
                result.Append("\t\t\t\t\t\t\t\t\t<div class=\"box_content\">\n");
            else
                result.Append(string.Format("\t\t\t\t\t\t\t\t\t<div class=\"box_content\" style=\"height: {0}px;\">\n", height));
            result.Append(string.Format("\t\t\t\t\t\t\t\t\t\t{0}\n", innerXml));
            result.Append("\t\t\t\t\t\t\t\t\t</div>\n");
            result.Append("\t\t\t\t\t\t\t\t</div>\n");
            result.Append("\t\t\t\t\t\t\t</div>\n");
            result.Append("\t\t\t\t\t\t</div>\n");
            result.Append("\t\t\t\t\t</div>\n");
            result.Append("\t\t\t\t</div>\n");
            result.Append("\t\t\t</div>\n");
            result.Append("\t\t</div>\n");
            result.Append("\t</div>\n");
            result.Append("</div>\n");
            
            return result.ToString();
        }        

        /// <summary>
        /// Gets the root attributes.
        /// </summary>
        /// <param name="doc">The XML document.</param>
        /// <param name="pattern">The pattern.</param>
        /// <param name="startDate">The start date/time.</param>
        private static void GetRootAttributes(XmlDocument doc, out string color, out string title, out string icon, out string height)
        {
            XmlNodeList root = doc.GetElementsByTagName("box");

            color = TryGetAttribute(root[0], "color");
            if (String.IsNullOrEmpty(color))
                color = "blue";

            title = TryGetAttribute(root[0], "title");            
            icon = TryGetAttribute(root[0], "icon");
            height = TryGetAttribute(root[0], "height");
        }

        /// <summary>
        /// Tries to get the value of an attribute.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="attribute">The name of the attribute.</param>
        /// <returns>The value of the attribute or <c>null</c> if no value is available.</returns>
        private static string TryGetAttribute(XmlNode node, string attribute)
        {
            XmlAttribute attr = node.Attributes[attribute];
            if (attr != null) return attr.Value;
            else return null;
        }

        private static void GetInnerXml(XmlDocument doc, out string innerXml)
        {
            XmlNodeList root = doc.GetElementsByTagName("box");

            innerXml = root[0].InnerXml;
        }

        /// <summary>
        /// Finds and removes the first occurrence of the XML markup.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <returns>The index-content data.</returns>
        private static KeyValuePair<int, string> FindAndRemoveFirstOccurrence(StringBuilder buffer)
        {
            Match match = XmlRegex.Match(buffer.ToString());

            if (match.Success)
            {
                buffer.Remove(match.Index, match.Length);

                return new KeyValuePair<int, string>(match.Index, match.Value);
            }

            return new KeyValuePair<int, string>(-1, null);
        }

        /// <summary>
        /// Logs a warning.
        /// </summary>
        /// <param name="message">The message.</param>
        private void LogWarning(string message)
        {
            if (_enableLogging)
            {
                _host.LogEntry(message, LogEntryType.Warning, null, this);
            }
        }

        /// <summary>
        /// Prepares the title of an item for display (always during phase 3).
        /// </summary>
        /// <param name="title">The input title.</param>
        /// <param name="context">The context information.</param>
        /// <returns>The prepared title (no markup allowed).</returns>
        public string PrepareTitle(string title, ContextInformation context)
        {
            return title;
        }

        /// <summary>
        /// Initializes the Storage Provider.
        /// </summary>
        /// <param name="host">The Host of the Component.</param>
        /// <param name="config">The Configuration data, if any.</param>
        /// <remarks>If the configuration string is not valid, the methoud should throw a <see cref="InvalidConfigurationException"/>.</remarks>
        public void Init(IHostV30 host, string config)
        {
            this._host = host;
            this._config = config != null ? config : "";

            if (this._config.ToLowerInvariant() == "nolog") _enableLogging = false;
        }

        /// <summary>
        /// Method invoked on shutdown.
        /// </summary>
        /// <remarks>This method might not be invoked in some cases.</remarks>
        public void Shutdown()
        {
            // Nothing to do
        }

        /// <summary>
        /// Gets the Information about the Provider.
        /// </summary>
        public ComponentInformation Information
        {
            get { return Info; }
        }

        /// <summary>
        /// Gets a brief summary of the configuration string format, in HTML. Returns <c>null</c> if no configuration is needed.
        /// </summary>
        public string ConfigHelpHtml
        {
            get { return null; }
        }
    }
}
